Goal: Dan suggested skipping the PCA step and just looking for metabolites associated with leaf length via penalized regression.

library(glmnet)
library(relaimpo)
library(tidyverse)
library(broom)

get leaflength data

leaflength <- read_csv("../../plant/output/leaf_lengths_metabolite.csv") %>%
  mutate(pot=str_pad(pot, width=3, pad="0"),
         sampleID=str_c("wyo", genotype, pot, sep="_")) %>%
  select(sampleID, genotype, trt, leaf_avg_std)

── Column specification ──────────────────────────────────────────────────────────────────────────
cols(
  pot = col_double(),
  soil = col_character(),
  genotype = col_character(),
  trt = col_character(),
  leaf_avg = col_double(),
  leaf_avg_std = col_double()
)
leaflength %>% arrange(sampleID)

get and wrangle metabolite data

met_raw <-read_csv("../input/metabolites_set1.csv")

── Column specification ──────────────────────────────────────────────────────────────────────────
cols(
  .default = col_double(),
  tissue = col_character(),
  soil = col_character(),
  genotype = col_character(),
  autoclave = col_character(),
  time_point = col_character(),
  concatenate = col_character()
)
ℹ Use `spec()` for the full column specifications.
met <- met_raw %>% 
  mutate(pot=str_pad(pot, width = 3, pad = "0")) %>%
  mutate(sampleID=str_c("wyo", genotype, pot, sep="_")) %>%
  select(sampleID, genotype, tissue, sample_mass = `sample_mass mg`, !submission_number:concatenate) %>%
  pivot_longer(!sampleID:sample_mass, names_to = "metabolite", values_to = "met_amount") %>%
  
  #remove unnamed metabolites
  filter(str_detect(metabolite, "[A-Z]|[a-z]")) %>%
  
  #adjust by sample mass
  mutate(met_per_mg=met_amount/sample_mass) %>%
  
  #scale and center
  group_by(metabolite, genotype, tissue) %>%
  mutate(met_per_mg=scale(met_per_mg),
         met_amt=scale(met_amount)
  ) %>% 
  pivot_wider(id_cols = sampleID, 
              names_from = c(tissue, metabolite), 
              values_from = starts_with("met_"),
              names_sep = "_")

met 

split this into two data frames, one normalized by tissue amount and one not.

met_per_mg <- met %>% select(sampleID, starts_with("met_per_mg")) %>%
  as.data.frame() %>% column_to_rownames("sampleID")
met_amt <- met %>% select(sampleID,  starts_with("met_amt")) %>%
  as.data.frame() %>% column_to_rownames("sampleID")

get leaf data order to match

leaflength <- leaflength[match(met$sampleID, leaflength$sampleID),]
leaflength

find metabolites sig for microbe

Live vs blank dead

normalized

met_per_mg_lm <- met_per_mg %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  mutate(trt=ifelse(str_detect(trt, "live"), "live", "blank_dead")) %>%
  pivot_longer(cols=starts_with("met"), names_to = "metabolite") %>%
  group_by(metabolite) %>%
  nest() %>%
  mutate(lm_trt=map(data, ~ lm(value ~ trt, data=.)),
         lm_leaf=map(data, ~ lm(leaf_avg_std ~ value, data=.)))
Joining, by = "sampleID"

raw

met_amt_lm <- met_amt %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  mutate(trt=ifelse(str_detect(trt, "live"), "live", "blank_dead")) %>%
  pivot_longer(cols=starts_with("met"), names_to = "metabolite") %>%
  group_by(metabolite) %>%
  nest() %>%
  mutate(lm_trt=map(data, ~ lm(value ~ trt, data=.)),
         lm_leaf=map(data, ~ lm(leaf_avg_std ~ value, data=.)))
Joining, by = "sampleID"
met_amt_lm_results<- met_amt_lm %>% mutate(broomtidy.trt = map(lm_trt, broom::tidy),
                         broomtidy.leaf = map(lm_leaf, broom::tidy)) %>%
  unnest(broomtidy.trt, broomtidy.leaf) %>%
  select(metabolite, term.trt=term, p.value.trt=p.value,
         term.leaf=term1, p.value.leaf=p.value1) %>%
  mutate(FDR.trt=p.adjust(p.value.trt, method = "fdr"),
         FDR.leaf=p.adjust(p.value.leaf, method = "fdr")) %>%
  filter(! str_detect(term.trt, "Intercept"),
         ! str_detect(term.leaf, "Intercept"))
unnest() has a new interface. See ?unnest for details.
Try `df %>% unnest(c(broomtidy.trt, broomtidy.leaf))`, with `mutate()` if needed
met_amt_lm_results %>% ungroup() %>%
  summarize(sig.trt=sum(FDR.trt<0.1), sig.leaf=sum(FDR.leaf<0.1), sig.both=sum(FDR.trt<0.1&FDR.leaf<0.1), count=n())

penalized regression for association with leaf

normalized

multi CV

Fit 101 CVs for each of 11 alphas

set.seed(1245)

folds <- tibble(run=1:101) %>% 
  mutate(folds=map(run, ~ sample(rep(1:6,6))))

system.time (met_per_mg_multiCV <- expand_grid(run=1:100, alpha=round(seq(0,1,.1),1)) %>%
               left_join(folds, by="run") %>%
               mutate(fit=map2(folds, alpha, ~ cv.glmnet(x=as.matrix(met_per_mg),
                                                         y=leaflength$leaf_avg_std, 
                                                         foldid = .x, alpha=.y
                                                         )))
             #, lambda=exp(seq(-5,0,length.out = 50)) )))
) #100 seconds
   user  system elapsed 
183.676  12.864 227.036 
head(met_per_mg_multiCV)

for each fit, pull out the mean cv error, lambda, min lambda, and 1se lambda

met_per_mg_multiCV <- met_per_mg_multiCV %>%
  mutate(cvm=map(fit, magrittr::extract("cvm")),
         lambda=map(fit, magrittr::extract("lambda")),
         lambda.min=map_dbl(fit, magrittr::extract("lambda.min" )),
         lambda.1se=map_dbl(fit, magrittr::extract("lambda.1se")),
         nzero=map(fit, magrittr::extract("nzero"))
  )

head(met_per_mg_multiCV)

now calculate the mean and sem of cvm and min,1se labmdas. These need to be done separately because of the way the grouping works

met_per_mg_summary_cvm <- met_per_mg_multiCV %>% dplyr::select(-fit, -folds) %>% 
  unnest(c(cvm, lambda)) %>%
  group_by(alpha, lambda) %>%
  summarize(meancvm=mean(cvm), sem=sd(cvm)/sqrt(n()), high=meancvm+sem, low=meancvm-sem)
`summarise()` has grouped output by 'alpha'. You can override using the `.groups` argument.
met_per_mg_summary_cvm
met_per_mg_summary_lambda <- met_per_mg_multiCV %>% dplyr::select(-fit, -folds, -cvm) %>% 
  group_by(alpha) %>%
  summarize(
    lambda.min.sd=sd(lambda.min), 
    lambda.min.mean=mean(lambda.min),
    #lambda.min.med=median(lambda.min), 
    lambda.min.high=lambda.min.mean+lambda.min.sd,
    #lambda.min.low=lambda.min.mean-lambda.min.sem,
    #lambda.1se.sem=sd(lambda.1se)/sqrt(n()), 
    lambda.1se.mean=mean(lambda.1se),
    #lambda.1se.med=median(lambda.1se), 
    #lambda.1se.high=lambda.1se+lambda.1se.sem,
    #lambda.1se.low=lambda.1se-lambda.1se.sem,
    nzero=nzero[1],
    lambda=lambda[1]
  )

met_per_mg_summary_lambda

plot it

met_per_mg_summary_cvm %>%
  #filter(alpha!=0) %>% # worse than everything else and throwing the plots off
  ggplot(aes(x=log(lambda), y= meancvm,  ymin=low, ymax=high)) +
  geom_ribbon(alpha=.25) +
  geom_line(aes(color=as.character(alpha))) +
  facet_wrap(~ as.character(alpha)) +
   coord_cartesian(xlim=(c(-5,0))) +
  geom_vline(aes(xintercept=log(lambda.min.mean)), alpha=.5, data=met_per_mg_summary_lambda) +
  geom_vline(aes(xintercept=log(lambda.min.high)), alpha=.5, data=met_per_mg_summary_lambda, color="blue") 

Make a plot of MSE at minimum lambda for each alpha

met_per_mg_summary_cvm %>% 
  group_by(alpha) %>%
  filter(rank(meancvm, ties.method = "first")==1) %>%
  ggplot(aes(x=alpha,y=meancvm,ymin=low,ymax=high)) +
  geom_ribbon(color=NA, fill="gray80") +
  geom_line() +
  geom_point()

Plot the number of nzero coefficients

met_per_mg_summary_lambda %>%
  unnest(c(lambda, nzero)) %>%
  group_by(alpha) %>%
  filter(abs(lambda.min.mean-lambda)==min(abs(lambda.min.mean-lambda))  ) %>%
  ungroup() %>%

ggplot(aes(x=as.character(alpha), y=nzero)) +
  geom_point() +
  ggtitle("Number of non-zero coefficents at minimum lambda") +
  ylim(0,50)

OK let’s do repeated test train starting from these CV lambdas

multi_tt <- function(lambda, alpha, n=10000, sample_size=36, train_size=30, x, y=leaflength$leaf_avg_std) {
  print(lambda)
  print(alpha)
tt <-
  tibble(run=1:n) %>%
  mutate(train=map(run, ~ sample(1:sample_size, train_size))) %>%
  mutate(fit=map(train, ~ glmnet(x=x[.,], y=y[.], lambda = lambda, alpha = alpha ))) %>%
  
  mutate(pred=map2(fit, train, ~ predict(.x, newx = x[-.y,]))) %>%
  mutate(cor=map2_dbl(pred, train, ~ cor(.x, y[-.y])  )) %>%
  mutate(MSE=map2_dbl(pred, train, ~ mean((y[-.y] - .x)^2))) %>%
  summarize(
    num_na=sum(is.na(cor)), 
    num_lt_0=sum(cor<=0, na.rm=TRUE),
    avg_cor=mean(cor, na.rm=TRUE),
    avg_MSE=mean(MSE))
tt
}

per_mg_fit_test_train <- met_per_mg_summary_lambda %>% 
  select(alpha, lambda.min.mean)

per_mg_fit_test_train <- met_per_mg_multiCV %>%
  filter(run==1) %>%
  select(alpha, fit) %>%
  right_join(per_mg_fit_test_train)
Joining, by = "alpha"
per_mg_fit_test_train <- per_mg_fit_test_train %>%
  mutate(pred_full=map2(fit, lambda.min.mean, ~ predict(.x, s=.y, newx=as.matrix(met_per_mg))),
         full_R=map_dbl(pred_full, ~ cor(.x, leaflength$leaf_avg_std)),
         full_MSE=map_dbl(pred_full, ~ mean((leaflength$leaf_avg_std-.x)^2))) %>%
  
  mutate(tt=map2(lambda.min.mean, alpha, ~ multi_tt(lambda=.x, alpha=.y, x=as.matrix(met_per_mg))))
[1] 23.42921
[1] 0
[1] 2.153769
[1] 0.1
[1] 1.359696
[1] 0.2
[1] 1.011106
[1] 0.3
[1] 0.8373424
[1] 0.4
[1] 0.7287725
[1] 0.5
[1] 0.6419847
[1] 0.6
[1] 0.5728218
[1] 0.7
the standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zero
[1] 0.5127878
[1] 0.8
the standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zero
[1] 0.461043
[1] 0.9
the standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zero
[1] 0.4188835
[1] 1
the standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zerothe standard deviation is zero
(per_mg_fit_test_train <- per_mg_fit_test_train %>% unnest(tt))
per_mg_fit_test_train %>%
  ggplot(aes(x=alpha)) +
  geom_line(aes(y=avg_cor), color="red") +
  geom_point(aes(y=avg_cor), color="red") +
  geom_line(aes(y=avg_MSE), color="blue") +
  geom_point(aes(y=avg_MSE), color="blue")

look at fit:

alpha_per_mg <- .1

best_per_mg <- per_mg_fit_test_train %>% filter(alpha == alpha_per_mg) 
best_per_mg_fit <- best_per_mg$fit[[1]]
best_per_mg_lambda <- best_per_mg$lambda.min.mean

per_mg_coef.tb <- coef(best_per_mg_fit, s=best_per_mg_lambda) %>% 
  as.matrix() %>% as.data.frame() %>% 
  rownames_to_column(var="metabolite") %>%
  rename(beta=`1`)
  
per_mg_coef.tb %>% filter(beta!=0) %>% arrange(desc(abs(beta)))
NA

pred and obs

plot(leaflength$leaf_avg_std, best_per_mg$pred_full[[1]])

cor.test(leaflength$leaf_avg_std, best_per_mg$pred_full[[1]]) #.57

    Pearson's product-moment correlation

data:  leaflength$leaf_avg_std and best_per_mg$pred_full[[1]]
t = 6.5696, df = 34, p-value = 1.583e-07
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.5559761 0.8641139
sample estimates:
      cor 
0.7479016 
best_per_mg$full_MSE
[1] 0.617451

merge with lm results for trt association

per_mg_coef.tb_and_lm <- per_mg_coef.tb %>%
  filter(beta !=0, str_detect(metabolite, "Intercept", negate = TRUE)) %>%
  left_join(met_per_mg_lm_results) %>%
  arrange(desc(abs(beta))) %>%
  select(-term.trt, -term.leaf)
Joining, by = "metabolite"
write_csv(per_mg_coef.tb_and_lm, "met_per_mg_elastic_leaf_lm_trt.csv")

per_mg_coef.tb_and_lm

non-normazlized

multi CV

Fit 101 CVs for each of 11 alphas

set.seed(1245)

folds <- tibble(run=1:101) %>% 
  mutate(folds=map(run, ~ sample(rep(1:6,6))))

system.time (met_amt_multiCV <- expand_grid(run=1:100, alpha=round(seq(0,1,.1),1)) %>%
               left_join(folds, by="run") %>%
               mutate(fit=map2(folds, alpha, ~ cv.glmnet(x=as.matrix(met_amt), y=leaflength$leaf_avg_std, foldid = .x, alpha=.y
                                                         )))
             #, lambda=exp(seq(-5,0,length.out = 50)) )))
) #100 seconds
   user  system elapsed 
150.207  10.925 202.175 
head(met_amt_multiCV)

for each fit, pull out the mean cv error, lambda, min lambda, and 1se lambda

met_amt_multiCV <- met_amt_multiCV %>%
  mutate(cvm=map(fit, magrittr::extract("cvm")),
         lambda=map(fit, magrittr::extract("lambda")),
         lambda.min=map_dbl(fit, magrittr::extract("lambda.min" )),
         lambda.1se=map_dbl(fit, magrittr::extract("lambda.1se")),
         nzero=map(fit, magrittr::extract("nzero"))
  )

head(met_amt_multiCV)

now calculate the mean and sem of cvm and min,1se labmdas. These need to be done separately because of the way the grouping works

met_amt_summary_cvm <- met_amt_multiCV %>% dplyr::select(-fit, -folds) %>% 
  unnest(c(cvm, lambda)) %>%
  group_by(alpha, lambda) %>%
  summarize(meancvm=mean(cvm), sem=sd(cvm)/sqrt(n()), high=meancvm+sem, low=meancvm-sem)
`summarise()` has grouped output by 'alpha'. You can override using the `.groups` argument.
met_amt_summary_cvm
met_amt_summary_lambda <- met_amt_multiCV %>% dplyr::select(-fit, -folds, -cvm) %>% 
  group_by(alpha) %>%
  summarize(
    lambda.min.sd=sd(lambda.min), 
    lambda.min.mean=mean(lambda.min),
    #lambda.min.med=median(lambda.min), 
    lambda.min.high=lambda.min.mean+lambda.min.sd,
    #lambda.min.low=lambda.min.mean-lambda.min.sem,
    #lambda.1se.sem=sd(lambda.1se)/sqrt(n()), 
    lambda.1se.mean=mean(lambda.1se),
    #lambda.1se.med=median(lambda.1se), 
    #lambda.1se.high=lambda.1se+lambda.1se.sem,
    #lambda.1se.low=lambda.1se-lambda.1se.sem,
    nzero=nzero[1],
    lambda=lambda[1]
  )

met_amt_summary_lambda

plot it

met_amt_summary_cvm %>%
  #filter(alpha!=0) %>% # worse than everything else and throwing the plots off
  ggplot(aes(x=log(lambda), y= meancvm,  ymin=low, ymax=high)) +
  geom_ribbon(alpha=.25) +
  geom_line(aes(color=as.character(alpha))) +
  facet_wrap(~ as.character(alpha)) +
   coord_cartesian(xlim=(c(-5,0))) +
  geom_vline(aes(xintercept=log(lambda.min.mean)), alpha=.5, data=met_amt_summary_lambda) +
  geom_vline(aes(xintercept=log(lambda.min.high)), alpha=.5, data=met_amt_summary_lambda, color="blue") 

Make a plot of MSE at minimum lambda for each alpha

met_amt_summary_cvm %>% 
  group_by(alpha) %>%
  filter(rank(meancvm, ties.method = "first")==1) %>%
  ggplot(aes(x=alpha,y=meancvm,ymin=low,ymax=high)) +
  geom_ribbon(color=NA, fill="gray80") +
  geom_line() +
  geom_point()

Plot the number of nzero coefficients

met_amt_summary_lambda %>%
  unnest(c(lambda, nzero)) %>%
  group_by(alpha) %>%
  filter(abs(lambda.min.mean-lambda)==min(abs(lambda.min.mean-lambda))  ) %>%
  ungroup() %>%

ggplot(aes(x=as.character(alpha), y=nzero)) +
  geom_point() +
  ggtitle("Number of non-zero coefficents at minimum lambda") +
  ylim(0,50)

OK let’s do repeated test train starting from these CV lambdas

multi_tt <- function(lambda, alpha, n=10000, sample_size=36, train_size=30, x, y=leaflength$leaf_avg_std) {
  print(lambda)
  print(alpha)
tt <-
  tibble(run=1:n) %>%
  mutate(train=map(run, ~ sample(1:sample_size, train_size))) %>%
  mutate(fit=map(train, ~ glmnet(x=x[.,], y=y[.], lambda = lambda, alpha = alpha ))) %>%
  
  mutate(pred=map2(fit, train, ~ predict(.x, newx = x[-.y,]))) %>%
  mutate(cor=map2_dbl(pred, train, ~ cor(.x, y[-.y])  )) %>%
  mutate(MSE=map2_dbl(pred, train, ~ mean((y[-.y] - .x)^2))) %>%
  summarize(
    num_na=sum(is.na(cor)), 
    num_lt_0=sum(cor<=0, na.rm=TRUE),
    avg_cor=mean(cor, na.rm=TRUE),
    avg_MSE=mean(MSE))
tt
}

amt_fit_test_train <- met_amt_summary_lambda %>% 
  select(alpha, lambda.min.mean)

amt_fit_test_train <- met_amt_multiCV %>%
  filter(run==1) %>%
  select(alpha, fit) %>%
  right_join(amt_fit_test_train)
Joining, by = "alpha"
amt_fit_test_train <- amt_fit_test_train %>%
  mutate(pred_full=map2(fit, lambda.min.mean, ~ predict(.x, s=.y, newx=as.matrix(met_amt))),
         full_R=map_dbl(pred_full, ~ cor(.x, leaflength$leaf_avg_std)),
         full_MSE=map_dbl(pred_full, ~ mean((leaflength$leaf_avg_std-.x)^2))) %>%
  
  mutate(tt=map2(lambda.min.mean, alpha, ~ multi_tt(lambda=.x, alpha=.y, x=as.matrix(met_amt))))
[1] 15.81867
[1] 0
[1] 1.628085
[1] 0.1
[1] 1.005012
[1] 0.2
[1] 0.7546315
[1] 0.3
[1] 0.602976
[1] 0.4
[1] 0.5073558
[1] 0.5
[1] 0.4406727
[1] 0.6
[1] 0.3961641
[1] 0.7
[1] 0.3522546
[1] 0.8
[1] 0.3196981
[1] 0.9
[1] 0.2933389
[1] 1
(amt_fit_test_train <- amt_fit_test_train %>% unnest(tt))
amt_fit_test_train %>%
  ggplot(aes(x=alpha)) +
  geom_line(aes(y=avg_cor), color="red") +
  geom_point(aes(y=avg_cor), color="red") +
  geom_line(aes(y=avg_MSE), color="blue") +
  geom_point(aes(y=avg_MSE), color="blue")

Not a ton of difference here. Ignoring 0.0, then 0.1 or 1 ar the best…kind of strange! ## look at fit:

alpha_amt <- .1

best_amt <- amt_fit_test_train %>% filter(alpha == alpha_amt) 
best_amt_fit <- best_amt$fit[[1]]
best_amt_lambda <- best_amt$lambda.min.mean

amt_coef.tb <- coef(best_amt_fit, s=best_amt_lambda) %>% 
  as.matrix() %>% as.data.frame() %>% 
  rownames_to_column(var="metabolite") %>%
  rename(beta=`1`)
  
amt_coef.tb %>% filter(beta!=0) %>% arrange(desc(abs(beta)))
NA

pred and obs

plot(leaflength$leaf_avg_std, best_amt$pred_full[[1]])

cor.test(leaflength$leaf_avg_std, best_amt$pred_full[[1]]) #.736

    Pearson's product-moment correlation

data:  leaflength$leaf_avg_std and best_amt$pred_full[[1]]
t = 9.3573, df = 34, p-value = 6.215e-11
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.7212855 0.9205609
sample estimates:
      cor 
0.8487052 
best_amt$full_MSE
[1] 0.4243047

merge with lm results for trt association

amt_coef.tb_and_lm <- amt_coef.tb %>%
  filter(beta !=0, str_detect(metabolite, "Intercept", negate = TRUE)) %>%
  left_join(met_amt_lm_results) %>%
  arrange(desc(abs(beta))) %>%
  select(-term.trt, -term.leaf)
Joining, by = "metabolite"
write_csv(amt_coef.tb_and_lm, "met_amt_elastic_leaf_lm_trt.csv")

amt_coef.tb_and_lm
LS0tCnRpdGxlOiAiRGlyZWN0IFBlbmFsaXplZCBSZWdyZXNzaW9uLS1NZXRhYm9saXRlcyIKYXV0aG9yOiAiSnVsaW4gTWFsb29mIgpkYXRlOiAiMy8wOS8yMDIxIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCkdvYWw6IERhbiBzdWdnZXN0ZWQgc2tpcHBpbmcgdGhlIFBDQSBzdGVwIGFuZCBqdXN0IGxvb2tpbmcgZm9yIG1ldGFib2xpdGVzIGFzc29jaWF0ZWQgd2l0aCBsZWFmIGxlbmd0aCB2aWEgcGVuYWxpemVkIHJlZ3Jlc3Npb24uCgpgYGB7cn0KbGlicmFyeShnbG1uZXQpCmxpYnJhcnkocmVsYWltcG8pCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGJyb29tKQpgYGAKCmdldCBsZWFmbGVuZ3RoIGRhdGEKYGBge3J9CmxlYWZsZW5ndGggPC0gcmVhZF9jc3YoIi4uLy4uL3BsYW50L291dHB1dC9sZWFmX2xlbmd0aHNfbWV0YWJvbGl0ZS5jc3YiKSAlPiUKICBtdXRhdGUocG90PXN0cl9wYWQocG90LCB3aWR0aD0zLCBwYWQ9IjAiKSwKICAgICAgICAgc2FtcGxlSUQ9c3RyX2MoInd5byIsIGdlbm90eXBlLCBwb3QsIHNlcD0iXyIpKSAlPiUKICBzZWxlY3Qoc2FtcGxlSUQsIGdlbm90eXBlLCB0cnQsIGxlYWZfYXZnX3N0ZCkKbGVhZmxlbmd0aCAlPiUgYXJyYW5nZShzYW1wbGVJRCkKYGBgCgpnZXQgYW5kIHdyYW5nbGUgbWV0YWJvbGl0ZSBkYXRhCmBgYHtyfQptZXRfcmF3IDwtcmVhZF9jc3YoIi4uL2lucHV0L21ldGFib2xpdGVzX3NldDEuY3N2IikKbWV0IDwtIG1ldF9yYXcgJT4lIAogIG11dGF0ZShwb3Q9c3RyX3BhZChwb3QsIHdpZHRoID0gMywgcGFkID0gIjAiKSkgJT4lCiAgbXV0YXRlKHNhbXBsZUlEPXN0cl9jKCJ3eW8iLCBnZW5vdHlwZSwgcG90LCBzZXA9Il8iKSkgJT4lCiAgc2VsZWN0KHNhbXBsZUlELCBnZW5vdHlwZSwgdGlzc3VlLCBzYW1wbGVfbWFzcyA9IGBzYW1wbGVfbWFzcyBtZ2AsICFzdWJtaXNzaW9uX251bWJlcjpjb25jYXRlbmF0ZSkgJT4lCiAgcGl2b3RfbG9uZ2VyKCFzYW1wbGVJRDpzYW1wbGVfbWFzcywgbmFtZXNfdG8gPSAibWV0YWJvbGl0ZSIsIHZhbHVlc190byA9ICJtZXRfYW1vdW50IikgJT4lCiAgCiAgI3JlbW92ZSB1bm5hbWVkIG1ldGFib2xpdGVzCiAgZmlsdGVyKHN0cl9kZXRlY3QobWV0YWJvbGl0ZSwgIltBLVpdfFthLXpdIikpICU+JQogIAogICNhZGp1c3QgYnkgc2FtcGxlIG1hc3MKICBtdXRhdGUobWV0X3Blcl9tZz1tZXRfYW1vdW50L3NhbXBsZV9tYXNzKSAlPiUKICAKICAjc2NhbGUgYW5kIGNlbnRlcgogIGdyb3VwX2J5KG1ldGFib2xpdGUsIGdlbm90eXBlLCB0aXNzdWUpICU+JQogIG11dGF0ZShtZXRfcGVyX21nPXNjYWxlKG1ldF9wZXJfbWcpLAogICAgICAgICBtZXRfYW10PXNjYWxlKG1ldF9hbW91bnQpCiAgKSAlPiUgCiAgcGl2b3Rfd2lkZXIoaWRfY29scyA9IHNhbXBsZUlELCAKICAgICAgICAgICAgICBuYW1lc19mcm9tID0gYyh0aXNzdWUsIG1ldGFib2xpdGUpLCAKICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IHN0YXJ0c193aXRoKCJtZXRfIiksCiAgICAgICAgICAgICAgbmFtZXNfc2VwID0gIl8iKQoKbWV0IApgYGAKCnNwbGl0IHRoaXMgaW50byB0d28gZGF0YSBmcmFtZXMsIG9uZSBub3JtYWxpemVkIGJ5IHRpc3N1ZSBhbW91bnQgYW5kIG9uZSBub3QuCmBgYHtyfQptZXRfcGVyX21nIDwtIG1ldCAlPiUgc2VsZWN0KHNhbXBsZUlELCBzdGFydHNfd2l0aCgibWV0X3Blcl9tZyIpKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lIGNvbHVtbl90b19yb3duYW1lcygic2FtcGxlSUQiKQptZXRfYW10IDwtIG1ldCAlPiUgc2VsZWN0KHNhbXBsZUlELCAgc3RhcnRzX3dpdGgoIm1ldF9hbXQiKSkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoInNhbXBsZUlEIikKYGBgCgpnZXQgbGVhZiBkYXRhIG9yZGVyIHRvIG1hdGNoCgpgYGB7cn0KbGVhZmxlbmd0aCA8LSBsZWFmbGVuZ3RoW21hdGNoKG1ldCRzYW1wbGVJRCwgbGVhZmxlbmd0aCRzYW1wbGVJRCksXQpsZWFmbGVuZ3RoCmBgYAoKIyMgZmluZCBtZXRhYm9saXRlcyBzaWcgZm9yIG1pY3JvYmUKCkxpdmUgdnMgYmxhbmsgZGVhZAoKbm9ybWFsaXplZApgYGB7cn0KbWV0X3Blcl9tZ19sbSA8LSBtZXRfcGVyX21nICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlSUQiKSAlPiUKICBsZWZ0X2pvaW4obGVhZmxlbmd0aCkgJT4lCiAgbXV0YXRlKHRydD1pZmVsc2Uoc3RyX2RldGVjdCh0cnQsICJsaXZlIiksICJsaXZlIiwgImJsYW5rX2RlYWQiKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9c3RhcnRzX3dpdGgoIm1ldCIpLCBuYW1lc190byA9ICJtZXRhYm9saXRlIikgJT4lCiAgZ3JvdXBfYnkobWV0YWJvbGl0ZSkgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZShsbV90cnQ9bWFwKGRhdGEsIH4gbG0odmFsdWUgfiB0cnQsIGRhdGE9LikpLAogICAgICAgICBsbV9sZWFmPW1hcChkYXRhLCB+IGxtKGxlYWZfYXZnX3N0ZCB+IHZhbHVlLCBkYXRhPS4pKSkKCmBgYAoKCmBgYHtyfQptZXRfcGVyX21nX2xtX3Jlc3VsdHM8LSBtZXRfcGVyX21nX2xtICU+JSBtdXRhdGUoYnJvb210aWR5LnRydCA9IG1hcChsbV90cnQsIGJyb29tOjp0aWR5KSwKICAgICAgICAgICAgICAgICAgICAgICAgIGJyb29tdGlkeS5sZWFmID0gbWFwKGxtX2xlYWYsIGJyb29tOjp0aWR5KSkgJT4lCiAgdW5uZXN0KGJyb29tdGlkeS50cnQsIGJyb29tdGlkeS5sZWFmKSAlPiUKICBzZWxlY3QobWV0YWJvbGl0ZSwgdGVybS50cnQ9dGVybSwgcC52YWx1ZS50cnQ9cC52YWx1ZSwKICAgICAgICAgdGVybS5sZWFmPXRlcm0xLCBwLnZhbHVlLmxlYWY9cC52YWx1ZTEpICU+JQogIG11dGF0ZShGRFIudHJ0PXAuYWRqdXN0KHAudmFsdWUudHJ0LCBtZXRob2QgPSAiZmRyIiksCiAgICAgICAgIEZEUi5sZWFmPXAuYWRqdXN0KHAudmFsdWUubGVhZiwgbWV0aG9kID0gImZkciIpKSAlPiUKICBmaWx0ZXIoISBzdHJfZGV0ZWN0KHRlcm0udHJ0LCAiSW50ZXJjZXB0IiksCiAgICAgICAgICEgc3RyX2RldGVjdCh0ZXJtLmxlYWYsICJJbnRlcmNlcHQiKSkKCm1ldF9wZXJfbWdfbG1fcmVzdWx0cyAlPiUgdW5ncm91cCgpICU+JQogIHN1bW1hcml6ZShzaWcudHJ0PXN1bShGRFIudHJ0PDAuMSksIHNpZy5sZWFmPXN1bShGRFIubGVhZjwwLjEpLCBzaWcuYm90aD1zdW0oRkRSLnRydDwwLjEmRkRSLmxlYWY8MC4xKSwgY291bnQ9bigpKQpgYGAKCnJhdwpgYGB7cn0KbWV0X2FtdF9sbSA8LSBtZXRfYW10ICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlSUQiKSAlPiUKICBsZWZ0X2pvaW4obGVhZmxlbmd0aCkgJT4lCiAgbXV0YXRlKHRydD1pZmVsc2Uoc3RyX2RldGVjdCh0cnQsICJsaXZlIiksICJsaXZlIiwgImJsYW5rX2RlYWQiKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9c3RhcnRzX3dpdGgoIm1ldCIpLCBuYW1lc190byA9ICJtZXRhYm9saXRlIikgJT4lCiAgZ3JvdXBfYnkobWV0YWJvbGl0ZSkgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZShsbV90cnQ9bWFwKGRhdGEsIH4gbG0odmFsdWUgfiB0cnQsIGRhdGE9LikpLAogICAgICAgICBsbV9sZWFmPW1hcChkYXRhLCB+IGxtKGxlYWZfYXZnX3N0ZCB+IHZhbHVlLCBkYXRhPS4pKSkKCmBgYAoKCmBgYHtyfQptZXRfYW10X2xtX3Jlc3VsdHM8LSBtZXRfYW10X2xtICU+JSBtdXRhdGUoYnJvb210aWR5LnRydCA9IG1hcChsbV90cnQsIGJyb29tOjp0aWR5KSwKICAgICAgICAgICAgICAgICAgICAgICAgIGJyb29tdGlkeS5sZWFmID0gbWFwKGxtX2xlYWYsIGJyb29tOjp0aWR5KSkgJT4lCiAgdW5uZXN0KGJyb29tdGlkeS50cnQsIGJyb29tdGlkeS5sZWFmKSAlPiUKICBzZWxlY3QobWV0YWJvbGl0ZSwgdGVybS50cnQ9dGVybSwgcC52YWx1ZS50cnQ9cC52YWx1ZSwKICAgICAgICAgdGVybS5sZWFmPXRlcm0xLCBwLnZhbHVlLmxlYWY9cC52YWx1ZTEpICU+JQogIG11dGF0ZShGRFIudHJ0PXAuYWRqdXN0KHAudmFsdWUudHJ0LCBtZXRob2QgPSAiZmRyIiksCiAgICAgICAgIEZEUi5sZWFmPXAuYWRqdXN0KHAudmFsdWUubGVhZiwgbWV0aG9kID0gImZkciIpKSAlPiUKICBmaWx0ZXIoISBzdHJfZGV0ZWN0KHRlcm0udHJ0LCAiSW50ZXJjZXB0IiksCiAgICAgICAgICEgc3RyX2RldGVjdCh0ZXJtLmxlYWYsICJJbnRlcmNlcHQiKSkKCm1ldF9hbXRfbG1fcmVzdWx0cyAlPiUgdW5ncm91cCgpICU+JQogIHN1bW1hcml6ZShzaWcudHJ0PXN1bShGRFIudHJ0PDAuMSksIHNpZy5sZWFmPXN1bShGRFIubGVhZjwwLjEpLCBzaWcuYm90aD1zdW0oRkRSLnRydDwwLjEmRkRSLmxlYWY8MC4xKSwgY291bnQ9bigpKQpgYGAKCgojIyAgcGVuYWxpemVkIHJlZ3Jlc3Npb24gZm9yIGFzc29jaWF0aW9uIHdpdGggbGVhZgoKIyBub3JtYWxpemVkCgojIyBtdWx0aSBDVgoKRml0IDEwMSBDVnMgZm9yIGVhY2ggb2YgMTEgYWxwaGFzCmBgYHtyfQpzZXQuc2VlZCgxMjQ1KQoKZm9sZHMgPC0gdGliYmxlKHJ1bj0xOjEwMSkgJT4lIAogIG11dGF0ZShmb2xkcz1tYXAocnVuLCB+IHNhbXBsZShyZXAoMTo2LDYpKSkpCgpzeXN0ZW0udGltZSAobWV0X3Blcl9tZ19tdWx0aUNWIDwtIGV4cGFuZF9ncmlkKHJ1bj0xOjEwMCwgYWxwaGE9cm91bmQoc2VxKDAsMSwuMSksMSkpICU+JQogICAgICAgICAgICAgICBsZWZ0X2pvaW4oZm9sZHMsIGJ5PSJydW4iKSAlPiUKICAgICAgICAgICAgICAgbXV0YXRlKGZpdD1tYXAyKGZvbGRzLCBhbHBoYSwgfiBjdi5nbG1uZXQoeD1hcy5tYXRyaXgobWV0X3Blcl9tZyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHk9bGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb2xkaWQgPSAueCwgYWxwaGE9LnkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkpCiAgICAgICAgICAgICAjLCBsYW1iZGE9ZXhwKHNlcSgtNSwwLGxlbmd0aC5vdXQgPSA1MCkpICkpKQopICMxMDAgc2Vjb25kcwoKaGVhZChtZXRfcGVyX21nX211bHRpQ1YpCmBgYAoKZm9yIGVhY2ggZml0LCBwdWxsIG91dCB0aGUgbWVhbiBjdiBlcnJvciwgbGFtYmRhLCBtaW4gbGFtYmRhLCBhbmQgMXNlIGxhbWJkYSAKYGBge3J9Cm1ldF9wZXJfbWdfbXVsdGlDViA8LSBtZXRfcGVyX21nX211bHRpQ1YgJT4lCiAgbXV0YXRlKGN2bT1tYXAoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgiY3ZtIikpLAogICAgICAgICBsYW1iZGE9bWFwKGZpdCwgbWFncml0dHI6OmV4dHJhY3QoImxhbWJkYSIpKSwKICAgICAgICAgbGFtYmRhLm1pbj1tYXBfZGJsKGZpdCwgbWFncml0dHI6OmV4dHJhY3QoImxhbWJkYS5taW4iICkpLAogICAgICAgICBsYW1iZGEuMXNlPW1hcF9kYmwoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibGFtYmRhLjFzZSIpKSwKICAgICAgICAgbnplcm89bWFwKGZpdCwgbWFncml0dHI6OmV4dHJhY3QoIm56ZXJvIikpCiAgKQoKaGVhZChtZXRfcGVyX21nX211bHRpQ1YpCmBgYAoKCm5vdyBjYWxjdWxhdGUgdGhlIG1lYW4gYW5kIHNlbSBvZiBjdm0gYW5kIG1pbiwxc2UgbGFibWRhcy4gIFRoZXNlIG5lZWQgdG8gYmUgZG9uZSBzZXBhcmF0ZWx5IGJlY2F1c2Ugb2YgdGhlIHdheSB0aGUgZ3JvdXBpbmcgd29ya3MKYGBge3J9Cm1ldF9wZXJfbWdfc3VtbWFyeV9jdm0gPC0gbWV0X3Blcl9tZ19tdWx0aUNWICU+JSBkcGx5cjo6c2VsZWN0KC1maXQsIC1mb2xkcykgJT4lIAogIHVubmVzdChjKGN2bSwgbGFtYmRhKSkgJT4lCiAgZ3JvdXBfYnkoYWxwaGEsIGxhbWJkYSkgJT4lCiAgc3VtbWFyaXplKG1lYW5jdm09bWVhbihjdm0pLCBzZW09c2QoY3ZtKS9zcXJ0KG4oKSksIGhpZ2g9bWVhbmN2bStzZW0sIGxvdz1tZWFuY3ZtLXNlbSkKCm1ldF9wZXJfbWdfc3VtbWFyeV9jdm0KYGBgCgpgYGB7cn0KbWV0X3Blcl9tZ19zdW1tYXJ5X2xhbWJkYSA8LSBtZXRfcGVyX21nX211bHRpQ1YgJT4lIGRwbHlyOjpzZWxlY3QoLWZpdCwgLWZvbGRzLCAtY3ZtKSAlPiUgCiAgZ3JvdXBfYnkoYWxwaGEpICU+JQogIHN1bW1hcml6ZSgKICAgIGxhbWJkYS5taW4uc2Q9c2QobGFtYmRhLm1pbiksIAogICAgbGFtYmRhLm1pbi5tZWFuPW1lYW4obGFtYmRhLm1pbiksCiAgICAjbGFtYmRhLm1pbi5tZWQ9bWVkaWFuKGxhbWJkYS5taW4pLCAKICAgIGxhbWJkYS5taW4uaGlnaD1sYW1iZGEubWluLm1lYW4rbGFtYmRhLm1pbi5zZCwKICAgICNsYW1iZGEubWluLmxvdz1sYW1iZGEubWluLm1lYW4tbGFtYmRhLm1pbi5zZW0sCiAgICAjbGFtYmRhLjFzZS5zZW09c2QobGFtYmRhLjFzZSkvc3FydChuKCkpLCAKICAgIGxhbWJkYS4xc2UubWVhbj1tZWFuKGxhbWJkYS4xc2UpLAogICAgI2xhbWJkYS4xc2UubWVkPW1lZGlhbihsYW1iZGEuMXNlKSwgCiAgICAjbGFtYmRhLjFzZS5oaWdoPWxhbWJkYS4xc2UrbGFtYmRhLjFzZS5zZW0sCiAgICAjbGFtYmRhLjFzZS5sb3c9bGFtYmRhLjFzZS1sYW1iZGEuMXNlLnNlbSwKICAgIG56ZXJvPW56ZXJvWzFdLAogICAgbGFtYmRhPWxhbWJkYVsxXQogICkKCm1ldF9wZXJfbWdfc3VtbWFyeV9sYW1iZGEKYGBgCgoKcGxvdCBpdApgYGB7cn0KbWV0X3Blcl9tZ19zdW1tYXJ5X2N2bSAlPiUKICAjZmlsdGVyKGFscGhhIT0wKSAlPiUgIyB3b3JzZSB0aGFuIGV2ZXJ5dGhpbmcgZWxzZSBhbmQgdGhyb3dpbmcgdGhlIHBsb3RzIG9mZgogIGdncGxvdChhZXMoeD1sb2cobGFtYmRhKSwgeT0gbWVhbmN2bSwgIHltaW49bG93LCB5bWF4PWhpZ2gpKSArCiAgZ2VvbV9yaWJib24oYWxwaGE9LjI1KSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj1hcy5jaGFyYWN0ZXIoYWxwaGEpKSkgKwogIGZhY2V0X3dyYXAofiBhcy5jaGFyYWN0ZXIoYWxwaGEpKSArCiAgIGNvb3JkX2NhcnRlc2lhbih4bGltPShjKC01LDApKSkgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bG9nKGxhbWJkYS5taW4ubWVhbikpLCBhbHBoYT0uNSwgZGF0YT1tZXRfcGVyX21nX3N1bW1hcnlfbGFtYmRhKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1sb2cobGFtYmRhLm1pbi5oaWdoKSksIGFscGhhPS41LCBkYXRhPW1ldF9wZXJfbWdfc3VtbWFyeV9sYW1iZGEsIGNvbG9yPSJibHVlIikgCgpgYGAKCk1ha2UgYSBwbG90IG9mIE1TRSBhdCBtaW5pbXVtIGxhbWJkYSBmb3IgZWFjaCBhbHBoYQoKYGBge3J9Cm1ldF9wZXJfbWdfc3VtbWFyeV9jdm0gJT4lIAogIGdyb3VwX2J5KGFscGhhKSAlPiUKICBmaWx0ZXIocmFuayhtZWFuY3ZtLCB0aWVzLm1ldGhvZCA9ICJmaXJzdCIpPT0xKSAlPiUKICBnZ3Bsb3QoYWVzKHg9YWxwaGEseT1tZWFuY3ZtLHltaW49bG93LHltYXg9aGlnaCkpICsKICBnZW9tX3JpYmJvbihjb2xvcj1OQSwgZmlsbD0iZ3JheTgwIikgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkKYGBgCgpQbG90IHRoZSBudW1iZXIgb2Ygbnplcm8gY29lZmZpY2llbnRzCgpgYGB7cn0KbWV0X3Blcl9tZ19zdW1tYXJ5X2xhbWJkYSAlPiUKICB1bm5lc3QoYyhsYW1iZGEsIG56ZXJvKSkgJT4lCiAgZ3JvdXBfYnkoYWxwaGEpICU+JQogIGZpbHRlcihhYnMobGFtYmRhLm1pbi5tZWFuLWxhbWJkYSk9PW1pbihhYnMobGFtYmRhLm1pbi5tZWFuLWxhbWJkYSkpICApICU+JQogIHVuZ3JvdXAoKSAlPiUKCmdncGxvdChhZXMoeD1hcy5jaGFyYWN0ZXIoYWxwaGEpLCB5PW56ZXJvKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2d0aXRsZSgiTnVtYmVyIG9mIG5vbi16ZXJvIGNvZWZmaWNlbnRzIGF0IG1pbmltdW0gbGFtYmRhIikgKwogIHlsaW0oMCw1MCkKYGBgCk9LIGxldCdzIGRvIHJlcGVhdGVkIHRlc3QgdHJhaW4gc3RhcnRpbmcgZnJvbSB0aGVzZSBDViBsYW1iZGFzCgpgYGB7cn0KbXVsdGlfdHQgPC0gZnVuY3Rpb24obGFtYmRhLCBhbHBoYSwgbj0xMDAwMCwgc2FtcGxlX3NpemU9MzYsIHRyYWluX3NpemU9MzAsIHgsIHk9bGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQpIHsKICBwcmludChsYW1iZGEpCiAgcHJpbnQoYWxwaGEpCnR0IDwtCiAgdGliYmxlKHJ1bj0xOm4pICU+JQogIG11dGF0ZSh0cmFpbj1tYXAocnVuLCB+IHNhbXBsZSgxOnNhbXBsZV9zaXplLCB0cmFpbl9zaXplKSkpICU+JQogIG11dGF0ZShmaXQ9bWFwKHRyYWluLCB+IGdsbW5ldCh4PXhbLixdLCB5PXlbLl0sIGxhbWJkYSA9IGxhbWJkYSwgYWxwaGEgPSBhbHBoYSApKSkgJT4lCiAgCiAgbXV0YXRlKHByZWQ9bWFwMihmaXQsIHRyYWluLCB+IHByZWRpY3QoLngsIG5ld3ggPSB4Wy0ueSxdKSkpICU+JQogIG11dGF0ZShjb3I9bWFwMl9kYmwocHJlZCwgdHJhaW4sIH4gY29yKC54LCB5Wy0ueV0pICApKSAlPiUKICBtdXRhdGUoTVNFPW1hcDJfZGJsKHByZWQsIHRyYWluLCB+IG1lYW4oKHlbLS55XSAtIC54KV4yKSkpICU+JQogIHN1bW1hcml6ZSgKICAgIG51bV9uYT1zdW0oaXMubmEoY29yKSksIAogICAgbnVtX2x0XzA9c3VtKGNvcjw9MCwgbmEucm09VFJVRSksCiAgICBhdmdfY29yPW1lYW4oY29yLCBuYS5ybT1UUlVFKSwKICAgIGF2Z19NU0U9bWVhbihNU0UpKQp0dAp9CgpwZXJfbWdfZml0X3Rlc3RfdHJhaW4gPC0gbWV0X3Blcl9tZ19zdW1tYXJ5X2xhbWJkYSAlPiUgCiAgc2VsZWN0KGFscGhhLCBsYW1iZGEubWluLm1lYW4pCgpwZXJfbWdfZml0X3Rlc3RfdHJhaW4gPC0gbWV0X3Blcl9tZ19tdWx0aUNWICU+JQogIGZpbHRlcihydW49PTEpICU+JQogIHNlbGVjdChhbHBoYSwgZml0KSAlPiUKICByaWdodF9qb2luKHBlcl9tZ19maXRfdGVzdF90cmFpbikKCnBlcl9tZ19maXRfdGVzdF90cmFpbiA8LSBwZXJfbWdfZml0X3Rlc3RfdHJhaW4gJT4lCiAgbXV0YXRlKHByZWRfZnVsbD1tYXAyKGZpdCwgbGFtYmRhLm1pbi5tZWFuLCB+IHByZWRpY3QoLngsIHM9LnksIG5ld3g9YXMubWF0cml4KG1ldF9wZXJfbWcpKSksCiAgICAgICAgIGZ1bGxfUj1tYXBfZGJsKHByZWRfZnVsbCwgfiBjb3IoLngsIGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkKSksCiAgICAgICAgIGZ1bGxfTVNFPW1hcF9kYmwocHJlZF9mdWxsLCB+IG1lYW4oKGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkLS54KV4yKSkpICU+JQogIAogIG11dGF0ZSh0dD1tYXAyKGxhbWJkYS5taW4ubWVhbiwgYWxwaGEsIH4gbXVsdGlfdHQobGFtYmRhPS54LCBhbHBoYT0ueSwgeD1hcy5tYXRyaXgobWV0X3Blcl9tZykpKSkKCgoKKHBlcl9tZ19maXRfdGVzdF90cmFpbiA8LSBwZXJfbWdfZml0X3Rlc3RfdHJhaW4gJT4lIHVubmVzdCh0dCkpCmBgYAoKYGBge3J9CnBlcl9tZ19maXRfdGVzdF90cmFpbiAlPiUKICBnZ3Bsb3QoYWVzKHg9YWxwaGEpKSArCiAgZ2VvbV9saW5lKGFlcyh5PWF2Z19jb3IpLCBjb2xvcj0icmVkIikgKwogIGdlb21fcG9pbnQoYWVzKHk9YXZnX2NvciksIGNvbG9yPSJyZWQiKSArCiAgZ2VvbV9saW5lKGFlcyh5PWF2Z19NU0UpLCBjb2xvcj0iYmx1ZSIpICsKICBnZW9tX3BvaW50KGFlcyh5PWF2Z19NU0UpLCBjb2xvcj0iYmx1ZSIpCmBgYAoKIyMgbG9vayBhdCBmaXQ6CgpgYGB7cn0KYWxwaGFfcGVyX21nIDwtIC4xCgpiZXN0X3Blcl9tZyA8LSBwZXJfbWdfZml0X3Rlc3RfdHJhaW4gJT4lIGZpbHRlcihhbHBoYSA9PSBhbHBoYV9wZXJfbWcpIApiZXN0X3Blcl9tZ19maXQgPC0gYmVzdF9wZXJfbWckZml0W1sxXV0KYmVzdF9wZXJfbWdfbGFtYmRhIDwtIGJlc3RfcGVyX21nJGxhbWJkYS5taW4ubWVhbgoKcGVyX21nX2NvZWYudGIgPC0gY29lZihiZXN0X3Blcl9tZ19maXQsIHM9YmVzdF9wZXJfbWdfbGFtYmRhKSAlPiUgCiAgYXMubWF0cml4KCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhcj0ibWV0YWJvbGl0ZSIpICU+JQogIHJlbmFtZShiZXRhPWAxYCkKICAKcGVyX21nX2NvZWYudGIgJT4lIGZpbHRlcihiZXRhIT0wKSAlPiUgYXJyYW5nZShkZXNjKGFicyhiZXRhKSkpCgpgYGAKCnByZWQgYW5kIG9icwpgYGB7cn0KcGxvdChsZWFmbGVuZ3RoJGxlYWZfYXZnX3N0ZCwgYmVzdF9wZXJfbWckcHJlZF9mdWxsW1sxXV0pCmNvci50ZXN0KGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkLCBiZXN0X3Blcl9tZyRwcmVkX2Z1bGxbWzFdXSkgIy43NQpiZXN0X3Blcl9tZyRmdWxsX01TRQpgYGAKCiMgbWVyZ2Ugd2l0aCBsbSByZXN1bHRzIGZvciB0cnQgYXNzb2NpYXRpb24KCmBgYHtyfQpwZXJfbWdfY29lZi50Yl9hbmRfbG0gPC0gcGVyX21nX2NvZWYudGIgJT4lCiAgZmlsdGVyKGJldGEgIT0wLCBzdHJfZGV0ZWN0KG1ldGFib2xpdGUsICJJbnRlcmNlcHQiLCBuZWdhdGUgPSBUUlVFKSkgJT4lCiAgbGVmdF9qb2luKG1ldF9wZXJfbWdfbG1fcmVzdWx0cykgJT4lCiAgYXJyYW5nZShkZXNjKGFicyhiZXRhKSkpICU+JQogIHNlbGVjdCgtdGVybS50cnQsIC10ZXJtLmxlYWYpCgp3cml0ZV9jc3YocGVyX21nX2NvZWYudGJfYW5kX2xtLCAiLi4vb3V0cHV0L21ldF9wZXJfbWdfZWxhc3RpY19sZWFmX2xtX3RydC5jc3YiKQoKcGVyX21nX2NvZWYudGJfYW5kX2xtCmBgYAoKCiMgbm9uLW5vcm1hemxpemVkCgojIyBtdWx0aSBDVgoKRml0IDEwMSBDVnMgZm9yIGVhY2ggb2YgMTEgYWxwaGFzCmBgYHtyfQpzZXQuc2VlZCgxMjQ1KQoKZm9sZHMgPC0gdGliYmxlKHJ1bj0xOjEwMSkgJT4lIAogIG11dGF0ZShmb2xkcz1tYXAocnVuLCB+IHNhbXBsZShyZXAoMTo2LDYpKSkpCgpzeXN0ZW0udGltZSAobWV0X2FtdF9tdWx0aUNWIDwtIGV4cGFuZF9ncmlkKHJ1bj0xOjEwMCwgYWxwaGE9cm91bmQoc2VxKDAsMSwuMSksMSkpICU+JQogICAgICAgICAgICAgICBsZWZ0X2pvaW4oZm9sZHMsIGJ5PSJydW4iKSAlPiUKICAgICAgICAgICAgICAgbXV0YXRlKGZpdD1tYXAyKGZvbGRzLCBhbHBoYSwgfiBjdi5nbG1uZXQoeD1hcy5tYXRyaXgobWV0X2FtdCksIHk9bGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQsIGZvbGRpZCA9IC54LCBhbHBoYT0ueQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSkKICAgICAgICAgICAgICMsIGxhbWJkYT1leHAoc2VxKC01LDAsbGVuZ3RoLm91dCA9IDUwKSkgKSkpCikgIzIwMCBzZWNvbmRzCgpoZWFkKG1ldF9hbXRfbXVsdGlDVikKYGBgCgpmb3IgZWFjaCBmaXQsIHB1bGwgb3V0IHRoZSBtZWFuIGN2IGVycm9yLCBsYW1iZGEsIG1pbiBsYW1iZGEsIGFuZCAxc2UgbGFtYmRhIApgYGB7cn0KbWV0X2FtdF9tdWx0aUNWIDwtIG1ldF9hbXRfbXVsdGlDViAlPiUKICBtdXRhdGUoY3ZtPW1hcChmaXQsIG1hZ3JpdHRyOjpleHRyYWN0KCJjdm0iKSksCiAgICAgICAgIGxhbWJkYT1tYXAoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibGFtYmRhIikpLAogICAgICAgICBsYW1iZGEubWluPW1hcF9kYmwoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibGFtYmRhLm1pbiIgKSksCiAgICAgICAgIGxhbWJkYS4xc2U9bWFwX2RibChmaXQsIG1hZ3JpdHRyOjpleHRyYWN0KCJsYW1iZGEuMXNlIikpLAogICAgICAgICBuemVybz1tYXAoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibnplcm8iKSkKICApCgpoZWFkKG1ldF9hbXRfbXVsdGlDVikKYGBgCgoKbm93IGNhbGN1bGF0ZSB0aGUgbWVhbiBhbmQgc2VtIG9mIGN2bSBhbmQgbWluLDFzZSBsYWJtZGFzLiAgVGhlc2UgbmVlZCB0byBiZSBkb25lIHNlcGFyYXRlbHkgYmVjYXVzZSBvZiB0aGUgd2F5IHRoZSBncm91cGluZyB3b3JrcwpgYGB7cn0KbWV0X2FtdF9zdW1tYXJ5X2N2bSA8LSBtZXRfYW10X211bHRpQ1YgJT4lIGRwbHlyOjpzZWxlY3QoLWZpdCwgLWZvbGRzKSAlPiUgCiAgdW5uZXN0KGMoY3ZtLCBsYW1iZGEpKSAlPiUKICBncm91cF9ieShhbHBoYSwgbGFtYmRhKSAlPiUKICBzdW1tYXJpemUobWVhbmN2bT1tZWFuKGN2bSksIHNlbT1zZChjdm0pL3NxcnQobigpKSwgaGlnaD1tZWFuY3ZtK3NlbSwgbG93PW1lYW5jdm0tc2VtKQoKbWV0X2FtdF9zdW1tYXJ5X2N2bQpgYGAKCmBgYHtyfQptZXRfYW10X3N1bW1hcnlfbGFtYmRhIDwtIG1ldF9hbXRfbXVsdGlDViAlPiUgZHBseXI6OnNlbGVjdCgtZml0LCAtZm9sZHMsIC1jdm0pICU+JSAKICBncm91cF9ieShhbHBoYSkgJT4lCiAgc3VtbWFyaXplKAogICAgbGFtYmRhLm1pbi5zZD1zZChsYW1iZGEubWluKSwgCiAgICBsYW1iZGEubWluLm1lYW49bWVhbihsYW1iZGEubWluKSwKICAgICNsYW1iZGEubWluLm1lZD1tZWRpYW4obGFtYmRhLm1pbiksIAogICAgbGFtYmRhLm1pbi5oaWdoPWxhbWJkYS5taW4ubWVhbitsYW1iZGEubWluLnNkLAogICAgI2xhbWJkYS5taW4ubG93PWxhbWJkYS5taW4ubWVhbi1sYW1iZGEubWluLnNlbSwKICAgICNsYW1iZGEuMXNlLnNlbT1zZChsYW1iZGEuMXNlKS9zcXJ0KG4oKSksIAogICAgbGFtYmRhLjFzZS5tZWFuPW1lYW4obGFtYmRhLjFzZSksCiAgICAjbGFtYmRhLjFzZS5tZWQ9bWVkaWFuKGxhbWJkYS4xc2UpLCAKICAgICNsYW1iZGEuMXNlLmhpZ2g9bGFtYmRhLjFzZStsYW1iZGEuMXNlLnNlbSwKICAgICNsYW1iZGEuMXNlLmxvdz1sYW1iZGEuMXNlLWxhbWJkYS4xc2Uuc2VtLAogICAgbnplcm89bnplcm9bMV0sCiAgICBsYW1iZGE9bGFtYmRhWzFdCiAgKQoKbWV0X2FtdF9zdW1tYXJ5X2xhbWJkYQpgYGAKCgpwbG90IGl0CmBgYHtyfQptZXRfYW10X3N1bW1hcnlfY3ZtICU+JQogICNmaWx0ZXIoYWxwaGEhPTApICU+JSAjIHdvcnNlIHRoYW4gZXZlcnl0aGluZyBlbHNlIGFuZCB0aHJvd2luZyB0aGUgcGxvdHMgb2ZmCiAgZ2dwbG90KGFlcyh4PWxvZyhsYW1iZGEpLCB5PSBtZWFuY3ZtLCAgeW1pbj1sb3csIHltYXg9aGlnaCkpICsKICBnZW9tX3JpYmJvbihhbHBoYT0uMjUpICsKICBnZW9tX2xpbmUoYWVzKGNvbG9yPWFzLmNoYXJhY3RlcihhbHBoYSkpKSArCiAgZmFjZXRfd3JhcCh+IGFzLmNoYXJhY3RlcihhbHBoYSkpICsKICAgY29vcmRfY2FydGVzaWFuKHhsaW09KGMoLTUsMCkpKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1sb2cobGFtYmRhLm1pbi5tZWFuKSksIGFscGhhPS41LCBkYXRhPW1ldF9hbXRfc3VtbWFyeV9sYW1iZGEpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PWxvZyhsYW1iZGEubWluLmhpZ2gpKSwgYWxwaGE9LjUsIGRhdGE9bWV0X2FtdF9zdW1tYXJ5X2xhbWJkYSwgY29sb3I9ImJsdWUiKSAKCmBgYAoKCk1ha2UgYSBwbG90IG9mIE1TRSBhdCBtaW5pbXVtIGxhbWJkYSBmb3IgZWFjaCBhbHBoYQoKYGBge3J9Cm1ldF9hbXRfc3VtbWFyeV9jdm0gJT4lIAogIGdyb3VwX2J5KGFscGhhKSAlPiUKICBmaWx0ZXIocmFuayhtZWFuY3ZtLCB0aWVzLm1ldGhvZCA9ICJmaXJzdCIpPT0xKSAlPiUKICBnZ3Bsb3QoYWVzKHg9YWxwaGEseT1tZWFuY3ZtLHltaW49bG93LHltYXg9aGlnaCkpICsKICBnZW9tX3JpYmJvbihjb2xvcj1OQSwgZmlsbD0iZ3JheTgwIikgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkKYGBgCgpQbG90IHRoZSBudW1iZXIgb2Ygbnplcm8gY29lZmZpY2llbnRzCgpgYGB7cn0KbWV0X2FtdF9zdW1tYXJ5X2xhbWJkYSAlPiUKICB1bm5lc3QoYyhsYW1iZGEsIG56ZXJvKSkgJT4lCiAgZ3JvdXBfYnkoYWxwaGEpICU+JQogIGZpbHRlcihhYnMobGFtYmRhLm1pbi5tZWFuLWxhbWJkYSk9PW1pbihhYnMobGFtYmRhLm1pbi5tZWFuLWxhbWJkYSkpICApICU+JQogIHVuZ3JvdXAoKSAlPiUKCmdncGxvdChhZXMoeD1hcy5jaGFyYWN0ZXIoYWxwaGEpLCB5PW56ZXJvKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2d0aXRsZSgiTnVtYmVyIG9mIG5vbi16ZXJvIGNvZWZmaWNlbnRzIGF0IG1pbmltdW0gbGFtYmRhIikgKwogIHlsaW0oMCw1MCkKYGBgCk9LIGxldCdzIGRvIHJlcGVhdGVkIHRlc3QgdHJhaW4gc3RhcnRpbmcgZnJvbSB0aGVzZSBDViBsYW1iZGFzCgpgYGB7cn0KbXVsdGlfdHQgPC0gZnVuY3Rpb24obGFtYmRhLCBhbHBoYSwgbj0xMDAwMCwgc2FtcGxlX3NpemU9MzYsIHRyYWluX3NpemU9MzAsIHgsIHk9bGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQpIHsKICBwcmludChsYW1iZGEpCiAgcHJpbnQoYWxwaGEpCnR0IDwtCiAgdGliYmxlKHJ1bj0xOm4pICU+JQogIG11dGF0ZSh0cmFpbj1tYXAocnVuLCB+IHNhbXBsZSgxOnNhbXBsZV9zaXplLCB0cmFpbl9zaXplKSkpICU+JQogIG11dGF0ZShmaXQ9bWFwKHRyYWluLCB+IGdsbW5ldCh4PXhbLixdLCB5PXlbLl0sIGxhbWJkYSA9IGxhbWJkYSwgYWxwaGEgPSBhbHBoYSApKSkgJT4lCiAgCiAgbXV0YXRlKHByZWQ9bWFwMihmaXQsIHRyYWluLCB+IHByZWRpY3QoLngsIG5ld3ggPSB4Wy0ueSxdKSkpICU+JQogIG11dGF0ZShjb3I9bWFwMl9kYmwocHJlZCwgdHJhaW4sIH4gY29yKC54LCB5Wy0ueV0pICApKSAlPiUKICBtdXRhdGUoTVNFPW1hcDJfZGJsKHByZWQsIHRyYWluLCB+IG1lYW4oKHlbLS55XSAtIC54KV4yKSkpICU+JQogIHN1bW1hcml6ZSgKICAgIG51bV9uYT1zdW0oaXMubmEoY29yKSksIAogICAgbnVtX2x0XzA9c3VtKGNvcjw9MCwgbmEucm09VFJVRSksCiAgICBhdmdfY29yPW1lYW4oY29yLCBuYS5ybT1UUlVFKSwKICAgIGF2Z19NU0U9bWVhbihNU0UpKQp0dAp9CgphbXRfZml0X3Rlc3RfdHJhaW4gPC0gbWV0X2FtdF9zdW1tYXJ5X2xhbWJkYSAlPiUgCiAgc2VsZWN0KGFscGhhLCBsYW1iZGEubWluLm1lYW4pCgphbXRfZml0X3Rlc3RfdHJhaW4gPC0gbWV0X2FtdF9tdWx0aUNWICU+JQogIGZpbHRlcihydW49PTEpICU+JQogIHNlbGVjdChhbHBoYSwgZml0KSAlPiUKICByaWdodF9qb2luKGFtdF9maXRfdGVzdF90cmFpbikKCmFtdF9maXRfdGVzdF90cmFpbiA8LSBhbXRfZml0X3Rlc3RfdHJhaW4gJT4lCiAgbXV0YXRlKHByZWRfZnVsbD1tYXAyKGZpdCwgbGFtYmRhLm1pbi5tZWFuLCB+IHByZWRpY3QoLngsIHM9LnksIG5ld3g9YXMubWF0cml4KG1ldF9hbXQpKSksCiAgICAgICAgIGZ1bGxfUj1tYXBfZGJsKHByZWRfZnVsbCwgfiBjb3IoLngsIGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkKSksCiAgICAgICAgIGZ1bGxfTVNFPW1hcF9kYmwocHJlZF9mdWxsLCB+IG1lYW4oKGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkLS54KV4yKSkpICU+JQogIAogIG11dGF0ZSh0dD1tYXAyKGxhbWJkYS5taW4ubWVhbiwgYWxwaGEsIH4gbXVsdGlfdHQobGFtYmRhPS54LCBhbHBoYT0ueSwgeD1hcy5tYXRyaXgobWV0X2FtdCkpKSkKCgoKKGFtdF9maXRfdGVzdF90cmFpbiA8LSBhbXRfZml0X3Rlc3RfdHJhaW4gJT4lIHVubmVzdCh0dCkpCmBgYAoKYGBge3J9CmFtdF9maXRfdGVzdF90cmFpbiAlPiUKICBnZ3Bsb3QoYWVzKHg9YWxwaGEpKSArCiAgZ2VvbV9saW5lKGFlcyh5PWF2Z19jb3IpLCBjb2xvcj0icmVkIikgKwogIGdlb21fcG9pbnQoYWVzKHk9YXZnX2NvciksIGNvbG9yPSJyZWQiKSArCiAgZ2VvbV9saW5lKGFlcyh5PWF2Z19NU0UpLCBjb2xvcj0iYmx1ZSIpICsKICBnZW9tX3BvaW50KGFlcyh5PWF2Z19NU0UpLCBjb2xvcj0iYmx1ZSIpCmBgYApOb3QgYSB0b24gb2YgZGlmZmVyZW5jZSBoZXJlLiBJZ25vcmluZyAwLjAsIHRoZW4gMC4xIG9yIDEgYXIgdGhlIGJlc3QuLi5raW5kIG9mIHN0cmFuZ2UhCiMjIGxvb2sgYXQgZml0OgoKYGBge3J9CmFscGhhX2FtdCA8LSAuMQoKYmVzdF9hbXQgPC0gYW10X2ZpdF90ZXN0X3RyYWluICU+JSBmaWx0ZXIoYWxwaGEgPT0gYWxwaGFfYW10KSAKYmVzdF9hbXRfZml0IDwtIGJlc3RfYW10JGZpdFtbMV1dCmJlc3RfYW10X2xhbWJkYSA8LSBiZXN0X2FtdCRsYW1iZGEubWluLm1lYW4KCmFtdF9jb2VmLnRiIDwtIGNvZWYoYmVzdF9hbXRfZml0LCBzPWJlc3RfYW10X2xhbWJkYSkgJT4lIAogIGFzLm1hdHJpeCgpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIHJvd25hbWVzX3RvX2NvbHVtbih2YXI9Im1ldGFib2xpdGUiKSAlPiUKICByZW5hbWUoYmV0YT1gMWApCiAgCmFtdF9jb2VmLnRiICU+JSBmaWx0ZXIoYmV0YSE9MCkgJT4lIGFycmFuZ2UoZGVzYyhhYnMoYmV0YSkpKQoKYGBgCgpwcmVkIGFuZCBvYnMKYGBge3J9CnBsb3QobGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQsIGJlc3RfYW10JHByZWRfZnVsbFtbMV1dKQpjb3IudGVzdChsZWFmbGVuZ3RoJGxlYWZfYXZnX3N0ZCwgYmVzdF9hbXQkcHJlZF9mdWxsW1sxXV0pICMuODUKYmVzdF9hbXQkZnVsbF9NU0UKYGBgCgojIG1lcmdlIHdpdGggbG0gcmVzdWx0cyBmb3IgdHJ0IGFzc29jaWF0aW9uCgpgYGB7cn0KYW10X2NvZWYudGJfYW5kX2xtIDwtIGFtdF9jb2VmLnRiICU+JQogIGZpbHRlcihiZXRhICE9MCwgc3RyX2RldGVjdChtZXRhYm9saXRlLCAiSW50ZXJjZXB0IiwgbmVnYXRlID0gVFJVRSkpICU+JQogIGxlZnRfam9pbihtZXRfYW10X2xtX3Jlc3VsdHMpICU+JQogIGFycmFuZ2UoZGVzYyhhYnMoYmV0YSkpKSAlPiUKICBzZWxlY3QoLXRlcm0udHJ0LCAtdGVybS5sZWFmKQoKd3JpdGVfY3N2KGFtdF9jb2VmLnRiX2FuZF9sbSwgIi4uL291dHB1dC9tZXRfYW10X2VsYXN0aWNfbGVhZl9sbV90cnQuY3N2IikKCmFtdF9jb2VmLnRiX2FuZF9sbQpgYGA=